package com.monster.starter.core.filter.limit;

import com.google.common.util.concurrent.RateLimiter;
import com.monster.exception.impl.MonsterNoTackException;
import com.monster.starter.core.common.MonsterExceptionEnum;
import com.monster.starter.core.common.config.http.HttpRequestConfig;
import com.monster.starter.core.utils.RequestMappingUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 方法限流拦截器
 *
 * @author kuang
 */
@Component
@Order(Integer.MIN_VALUE + 20)
public class LimitFilter extends OncePerRequestFilter {
    static class LimitInfo {
        public LimitInfo(Limit limit, RateLimiter rateLimiter) {
            this.limit = limit;
            this.rateLimiter = rateLimiter;
        }

        Limit limit;
        RateLimiter rateLimiter;
    }

    private static final String LOCK = "monster-limit-lock";
    /**
     * 请求配置
     */
    private HttpRequestConfig requestConfig;
    /**
     * 整体令牌桶限流
     */
    private RateLimiter limiter = null;
    /**
     * 存放单个方法限流的集合
     */
    private static final Map<String, LimitInfo> limiters = new HashMap<>();

    /**
     * 创建一个限流器
     * @param url 唯一key
     * @param timeOut 获取令牌超时时间
     * @param rateLimiter 限流器
     */
    public static void addLimit (String url, long timeOut, RateLimiter rateLimiter) {
        if (!limiters.containsKey(url)) {
            limiters.put(url, new LimitInfo(new Limit(){
                @Override
                public Class<? extends Annotation> annotationType() {
                    return null;
                }

                @Override
                public long initTime() {
                    return 0;
                }

                @Override
                public double maxNumber() {
                    return 0;
                }

                @Override
                public long timeOut() {
                    return timeOut;
                }
            }, rateLimiter));
        }
    }

    /**
     * 初始化配置对象
     * @param requestConfig 请求配置对象
     */
    @Autowired
    private void setRequestConfig(HttpRequestConfig requestConfig) {
        this.requestConfig = requestConfig;
        if (requestConfig.isLimit()) {
            if (requestConfig.getLimitInitTime() < 1) {
                requestConfig.setLimitInitTime(1);
            }
            if (requestConfig.getLimitAll() != null) {
                if (requestConfig.getLimitAll() < 0) {
                    throw new RuntimeException("monster.http.request.limitAll不能配置小于0!");
                }
                limiter = RateLimiter.create(requestConfig.getLimitAll(), requestConfig.getLimitInitTime(), TimeUnit.SECONDS);
            }
        }
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (limiter != null) {
            //获取令牌桶中一个令牌，最多等待10秒
            if (!limiter.tryAcquire(requestConfig.getLimitTimeOut(), TimeUnit.SECONDS)) {
                throw new MonsterNoTackException(MonsterExceptionEnum.LIMIT_ERROR, "系统达到最大限制负荷，整体限流");
            }
        }
        if (requestConfig.isLimit()) {
            if (!limiters.containsKey(request.getRequestURI())) {
                synchronized (LOCK) {
                   if(!limiters.containsKey(request.getRequestURI())) {
                       // 获取注解
                       HandlerMethod handlerMethod = RequestMappingUtils.getHandlerMethod(request);
                       if (handlerMethod != null) {
                           // 获取方法
                           Method method = handlerMethod.getMethod();
                           // 获取注解
                           Limit limit = method.getDeclaredAnnotation(Limit.class);
                           if (limit != null) {
                               limiters.put(request.getRequestURI(), new LimitInfo(limit, RateLimiter.create(limit.maxNumber(), limit.initTime(), TimeUnit.SECONDS)));
                           } else {
                               limiters.put(request.getRequestURI(), null);
                           }
                       }
                    }
                }
            }
            LimitInfo info = limiters.get(request.getRequestURI());
            if (info != null && !info.rateLimiter.tryAcquire(info.limit.timeOut(), TimeUnit.SECONDS)) {
                throw new MonsterNoTackException(MonsterExceptionEnum.LIMIT_ERROR, "单个方法达到最大限制负荷，限制访问");
            }
        }
        filterChain.doFilter(request, response);

    }
}
